Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BE] 레이싱게임_고준보 미션 연습 #520

Open
wants to merge 21 commits into
base: main
Choose a base branch
from

Conversation

Ridealist
Copy link

  • 코드 리뷰 부탁드립니다!
  • 저도 맞리뷰 가겠습니다:)

Input에서 사용자 이름을 받아 Player 객체 생성하도록
한 라운드 진행할 때 playOneRound 메세지 받아 수행
ParameterizedTest 사용해 단위 테스트 구현
게임 라운드 계속 진행 여부 관리 메서드, 게임 진행 메서드 구현
게임 진행에 따른 메서드 반환값 변환 단위 테스트 구현
콤마 포함 여부, 이름 길이 검사, 숫자 여부 검사 메소드 구현
view에서 입력값 받아 옴, 잘못된 입력값에 error 메세지 출력 후 재요청
domain 객체들 생성 및 연결
view에서 게임 상황 및 결과 출력 메소드 호출
Copy link

@khj1 khj1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

안녕하세요 준보님! 맞리뷰 왔습니다 :)

스트림을 활용하는 방식과 객체 분리에 대해서만 더 공부해보시면 훨씬 좋은 코드를 작성하실 수 있을 것 같아요!

저도 객체 분리에 대해서 고민을 정말 많이했는데요! <객체지향의 사실과 오해> 라는 책이 정말 많은 도움이 됐어요! 책을 직접 읽으시는 게 좋지만 그럴 여건이 안되신다면

이 자료를 읽어보시는 것을 강력히 추천드립니다!

또한 스트림에 대해서도 공부해보시면 좋을 것 같아요! 개인적으로 <모던 자바 인 액션>을 직접 읽어보시는 것을 추천드려요! 저도 지금 읽고 있지만 정말 많은 인사이트를 얻고 있습니다.

제가 정리한 내용 중에 준보님에게 지금 당장 도움이 될만한 내용을 첨부 해드리겠습니다!

다음 자료들도 함께 보시면 더 좋을 것 같습니다!!!

너무 주절 주절 말이 길어졌네요!
리뷰 해주셔서 감사합니다 준보님 항상 응원할게요! :)

public class GameResult {

private final Player player;
private Map<String, Integer> playerPosition = new LinkedHashMap<>();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

일급 컬렉션을 사용하는 것도 좋다고 생각해요!

추가로 playerPosition 상태를 활용하신 이유가 궁금합니다!

게임이 진행될 때마다 Car 객체 내부에서 nameposition 값을 불러와서 저장하는 용도로 사용하고 계신 것 같아요. 마치 repository 처럼요! 그런데 굳이 실시간으로 변하는 position 값을 따로 저장할 필요가 있을 지 고민해보시는 게 좋을 것 같아요! 이미 Car 객체가 최신화 된 position 값을 저장하고 있기 때문에 이를 활용하시는 게 더 합리적일 것 같습니다!

게임 결과를 편하게 출력하기 위한 용도로 playerPosition 을 사용하신 건 아닐까? 라는 생각도 드네요. 그럼에도 GameResult 보다는 RacingGame 이나, Player 객체에서 라운드 마다 게임 결과를 반환하는 책임을 수행하는 게 더 적절해 보여요!

GameResultRacingGame 두 객체 모두 Player에 의존하고 있어요. 이런 경우에 해당 객체에 적절한 책임이 부여됐는지 한번 고민해보시면 좋을 것 같습니다!

}

public void moveOrStop(NumberGenerator numberGenerator) {
if (numberGenerator.generate() >= MOVING_CRITERIA) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

해당 조건문을 메서드로 분리해서 어떤 역할을 하는 지 이름을 지어주는 것이 좋다고 생각해요!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

한준님 말씀에 동의합니다. moveOrStop 이름이 1.이동할 수 있을지 판단하는 역할과 2.자동차를 이동시키는 역할 2가지를 함께 하고 있어, 한가지 역할만 해야하는 메소드 원칙에 위배되었네요... 피드백 감사합니다:)

import racingcar.domain.car.Car;
import racingcar.domain.car.NumberGenerator;

public class Player {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Player 내부에서 복수의 Car 객체들을 다루고 있네요!
Player 보단 Players 처럼 복수형으로 이름을 지어주시는 게 더 타당해보입니다!

Copy link
Author

@Ridealist Ridealist Dec 4, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

네 동의합니다. 일반적으로 클래스명에 단수를 써야한다는(?) 저만의 강박이 있었는데, 한준님 피드백 덕분에 제 고정관념을 깨게 되었습니다. stackoverflow에서도 제 생각과 비슷한 고민을 한 분이 있었네요...ㅎㅎ 감사합니다!
https://stackoverflow.com/questions/11673750/is-it-good-practice-for-java-class-names-to-be-plural

Comment on lines +31 to +33
public List<Car> getRacingCars() {
return racingCars;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getter()로 리스트를 그대로 반환하는 것인 정말 위험하다고 생각해요!
리스트를 반환하더라도 실제로 반환되는 것은 해당 리스트의 참조 값입니다.

만약 다음과 같은 코드가 작성되면 외부에서 Player 내부의 상태가 쉽게 바뀔 수 있어요.

List<Car> cars = player.getRacingCars();
cars = new ArrayList<Car>();

따라서 리스트를 반환할 때는 불변 객체로 변환해주시는 게 안전해요!
Collections.unmodifiableList() 를 활용해보시는 것을 추천드립니다!

Copy link
Author

@Ridealist Ridealist Dec 4, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

한준님의 코드에서 이 부분의 의도가 궁금했는데, 이런 깊은 뜻이 있었군요! 조언 정말 감사합니다:)

한준님 덕분에 더 공부해봤는데, List 필드에 값을 지정할 때 아예 unmodifiableList를 적용해주면 어떨까 싶어
자료들 찾아봤고, 아래 자료 읽어보면 좋을 것 같아 공유드립니다:)
남겨주신 리뷰로 많은 공부 했습니다 감사합니다!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

좋은 자료 감사합니다!! 일급 컬렉션을 반환할 때 뿐만 아니라 생성할 떄도 불변성을 고려해줘야 하는군요...!
덕분에 방어적 복사에 대해서도 알 수 있었어요! 정말 감사합니다!!!! 👍👍 👍 👍 👍

Comment on lines +23 to +28
InputView inputView = new InputView();
List<String> playerNames = repeat(inputView::readPlayerName);
Player player = new Player(playerNames);
this.gameResult = new GameResult(player);
int gameRound = repeat(inputView::readGameRound);
this.racingGame = new RacingGame(player, gameRound);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

setUp() 메서드 내부의 기능들도 분리해보시는 것이 어떨까요?

setUp() 메서드는 결국 RacingGame 생성을 최종 목표로 하고 있네요.
그렇다면 RacingGame 이 생성되는 과정들을 적절하게 묶어서 메서드로 분리해보시는 것이
가독성 측면에서 더 좋을 것 같습니다!

Comment on lines +7 to +9
private static final String NOT_IN_COMMA = "자동차 이름을 쉼표(,)로 구분해 적어주세요.";
private static final String INVALID_NAME_LENGTH = "자동차 이름을 5글자 이내로 입력해주세요.";
private static final String NOT_NUMBER = "시도할 횟수를 숫자로 입력해주세요.";
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

InputValidator 에서 거의 모든 예외를 전부 처리하고 있어요.

자동차 이름 길이와 같은 제약 사항은 도메인에서 관리해주는 것이 더 타당하다고 생각합니다!
입력 단계에서 관리해 줄 예외와 도메인 단에서 관리해 줄 예외를 구분해보시는 것을 추천드려요!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

한준님 말씀에 동의합니다.

저도 모든 validation을 InputValidator에서 처리하는 구조가 BE 입장에서도 안 좋다고 생각하긴 했습니다:)
현준님 말씀처럼 비즈니스 로직 문제도 그렇고, view는 결국 FE 단인데 FE에서 넘어온 데이터에 대한 유효성 검증 책임이 BE에도 있다고 생각해서요ㅎㅎ 다만 그 Exception 처리를 controller에서 더 쉽게 하기 위해, 어쩔 수 없이 InputValidator에 모든 책임을 맡겼습니다.

현준님 코드 보면서 controller에서 도메인 객체를 생성하는 부분만 메소드화 시켜 exception 처리 메소드에 대입해서 사용하신걸 보고 적절한 처리 방법을 배웠습니다. 감사합니다!

Comment on lines +15 to +16
InputValidator.notContainsComma(input);
String[] inputList = InputValidator.moreThanLenFive(input);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

메서드 네임에 Comma 와 같은 너무 구체적인 사항이 들어가는 것은 좋지 않다고 생각해요.
만약에 자동차 이름을 구분하는 단위가 , 에서 : 로 변했다면 관련된 메서드 이름까지 바꿔줘야 하는 번거로움이 있습니다.

moreThanLenFive 도 마찬가지로 자동차 이름을 6자까지 허용해준다면? 메서드 네이밍이 변해야겠죠.
이처럼 이름에 자료형이나 구체적인 값을 포함시키는 것은 유지보수에 매우 불리하게 작용합니다!

추가로 이름을 축약시키는 것도 지양해야한다고 알고있어요! 이름이 길어지더라도 Len 보다는 Length 를 그대로 사용하시는 것이 더 이해하기 쉽다고 생각합니다!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

맞습니다. 이건 부끄럽네요ㅎㅎ...
예전에 다른 분께도 메소드 네이밍에 대해 지적받은 부분인데 아직 고치지 못하고 있었습니다.
말씀하신 내용 다 동의하고, 지나치게 구체적인 메서드 네이밍이나 축약 표현 지향하도록 노력하겠습니다.
감사합니다!

Comment on lines +14 to +15
NumberGenerator numberGenerator = new TestNumberGenerator(3);
Car car = new Car("June");
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

테스트 코드에서 반복적으로 사용되는 값이나 객체를 어떻게 처리할 지
고민해보시는 것도 좋을 것 같아요!

@BeforeEach 와 같은 테스트 라이프 사이클을 활용해보시는 건 어떨까요?!

player.playOneRound(numberGenerator);
List<Car> racingCars = player.getRacingCars();
for (Car car : racingCars) {
assertThat(car.getPosition()).isEqualTo(position);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getter() 지양하는 것이 좋지만 무조건 배제하는 것은 힘들다고 생각해요.
값을 출력해야할 때나 해당 값으로 연산을 진행해야할 때 어쩔 수 없이 getter()를 사용하게 되는 경우들이 있습니다.

다만 값을 비교할 때는 getter()를 활용하지 않고 메시지를 보내는 것 만으로 충분하다고 생각해요.
나랑 위치가 같니?, 나보다 위치가 더 머니? 와 같은 메시지들을 떠올려서 이에 맞는 메서드를 구현해보시는 것을 추천드려요!!

Comment on lines +34 to +46
class TestNumberGenerator implements NumberGenerator {

private final int number;

public TestNumberGenerator(int number) {
this.number = number;
}

@Override
public int generate() {
return number;
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TestNumberGenerator도 여러 코드에서 반복적으로 활용되고 있습니다.
Test 패키지 내에서 별도의 클래스로 분리하거나 따로 import 해서 중복을 제거해보시는 것은 어떨까요?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants